package rs.etf.mips.crossroad.logic;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

/**
 * Traffic space in the center of the crossroad.
 * 
 * @author Momcilo
 *
 */
public class CrossroadCenter implements ITrafficSpace {

	private static final int streetCnt = 4;

	/**
	 * The corner of the central square.
	 * 
	 * @author Momcilo
	 * 
	 */
	private enum Corner {
		UpLeft, UpRight, DownLeft, DownRight
	}

	/**
	 * The direction of the rotation.
	 * 
	 * @author Momcilo
	 * 
	 */
	private enum RotationDirection {
		Clockwise, Counterclockwise
	}

	/**
	 * 
	 * Saves the data about the vehicles rotational movement
	 * 
	 * @author Momcilo
	 * 
	 */
	private class TurningVehicle {
		/**
		 * the vehicle that rotates
		 */
		private Vehicle vehicle;

		/**
		 * corner in the center of rotation movement
		 */
		private Corner corner;

		/**
		 * rotation direction: clockwise/counterclockwise
		 */
		private RotationDirection rotation;

		/**
		 * elapsed time since the beginning of the rotation
		 */
		private int time;

		/**
		 * rotation half diameter
		 */
		private double r;

		public Vehicle getVehicle() {
			return vehicle;
		}

		public void setVehicle(Vehicle vehicle) {
			this.vehicle = vehicle;
		}

		public Corner getCorner() {
			return corner;
		}

		public void setCorner(Corner corner) {
			this.corner = corner;
		}

		public RotationDirection getRotation() {
			return rotation;
		}

		public void setRotation(RotationDirection rotation) {
			this.rotation = rotation;
		}

		public int getTime() {
			return time;
		}

		public void setTime(int time) {
			this.time = time;
		}

		public double getR() {
			return r;
		}

		public void setR(double r) {
			this.r = r;
		}

		public void incTime() {
			++this.time;
		}

		public TurningVehicle(Vehicle vehicle, Corner corner,
				RotationDirection rotation, double r) {
			super();
			this.setVehicle(vehicle);
			this.setCorner(corner);
			this.setRotation(rotation);
			this.setR(r);
			this.setTime(0);
		}
	}

	/**
	 * Map vehicles that are turning left/right to corresponding data
	 */
	private HashMap<Vehicle, TurningVehicle> turniningVehicles;

	/**
	 * List of all vehicles in the center of the crossroad
	 */
	private List<Vehicle> allVehicles;

	//
	// 0 5 6 11 12 17 18 23
	// [|||||] [|||||] [|||||] [|||||]
	// south east north west
	//
	/**
	 * List of lines : 0..5 south, 6..11 east, 12..17 north, 18..23 west
	 */
	private List<ITrafficSpace> tracks;

	/**
	 * Mapping line:id
	 */
	private Hashtable<ITrafficSpace, Integer> index;

	/**
	 * line speed of vehicles
	 */
	private double speed;

	/**
	 * the size of central square.
	 */
	private double size;

	public CrossroadCenter(double speed, double size) {
		super();
		this.speed = speed;
		this.size = size;
		this.index = new Hashtable<>(streetCnt * Street.lineCount);
		this.tracks = new ArrayList<>(streetCnt * Street.lineCount);
		this.allVehicles = new ArrayList<>();
		this.turniningVehicles = new HashMap<>();
	}

	/**
	 * Connects the street to the center
	 * 
	 * @param street
	 * @param side
	 */
	public void connectStreet(Street street, StreetDirection side) {
		int startIndex = 0;
		switch (side) {
		case East:
			startIndex = Street.lineCount;
			break;
		case North:
			startIndex = 2 * Street.lineCount;
			break;
		case West:
			startIndex = 3 * Street.lineCount;
			break;
		case South:
			startIndex = 0;
			break;
		default:
			break;
		}

		for (int i = 0; i < Street.lineCount; i++) {
			tracks.add(i + startIndex, street.getLine(i));
			index.put(street.getLine(i), i + startIndex);
		}
		for (int i = 3; i < Street.lineCount; i++) {
			street.getLine(i).setNeighborTrafficSpace(this);
		}
	}

	@Override
	public Iterator<Vehicle> iterator() {
		return allVehicles.iterator();
	}

	@Override
	public void update() {
		for (int i = allVehicles.size() - 1; i >= 0; i--) {
			move(allVehicles.get(i));
		}
	}

	@Override
	public boolean move(Vehicle vehicle) {
		if (turniningVehicles.containsKey(vehicle)) {
			rotateVehicle(turniningVehicles.get(vehicle));
		} else {
			translateVehicle(vehicle);
		}
		return true;
	}

	@Override
	public boolean canMove(Vehicle vehicle) {
		// XXX collisions not included
		return true;
	}

	@Override
	public boolean isFree(double x, double y) {
		// XXX collisions not included
		return true;
	}

	@Override
	public boolean addVehicle(Vehicle vehicle) {
		return allVehicles.add(vehicle);
	}

	@Override
	public boolean addVehicle(Vehicle vehicle, ITrafficSpace neighbor) {
		Integer map = index.get(neighbor);// get the lines' id
		if (map == null)
			return false;

		int pos = map % Street.lineCount; // pos = 3, 4, 5
		int side = map / Street.lineCount; // side = 0, 1, 2, 3 | south, east,
											// north, west

		double lineWidth = size / Street.lineCount; // width of one line

		double r = 0; // half diameter of rotational movement, if zero vehicle
						// is moving translator
		switch (pos) {
		case 3:
			r = 3.5 * lineWidth;
			break;
		case 5:
			r = 0.5 * lineWidth;
			break;
		default:
			r = 0;
			break;
		}

		if (r == 0) {
			// translation
			// 0 - south
			// 1 - east
			// 2 - north
			// 3 - west
			switch (side) {
			case 0:
				// south
				vehicle.setParameters(4.5 * lineWidth, this.size, 0,
						-this.speed, this);
				break;
			case 1:
				// east
				vehicle.setParameters(this.size, 1.5 * lineWidth, -this.speed,
						0, this);
				break;
			case 2:
				// north
				vehicle.setParameters(1.5 * lineWidth, 0, 0,
						this.speed, this);
				break;
			case 3:
				// west
				vehicle.setParameters(0, 4.5 * lineWidth, this.speed, 0, this);
				break;
			default:
				// can not happen
				vehicle.setParameters(0, 0, 0, 0, this);
				break;
			}
		} else {
			// rotation
			// 0 - south
			// 1 - east
			// 2 - north
			// 3 - west
			Corner corner = null;
			RotationDirection rd = null;
			if ((side == 3 && r < lineWidth)
					|| (side == 0 && r > 3 * lineWidth))
				corner = Corner.DownLeft;// west[5] || south[3]
			else if ((side == 0 && r < lineWidth)
					|| (side == 1 && r > 3 * lineWidth))
				corner = Corner.DownRight;// south[5] || east[3]
			else if ((side == 1 && r < lineWidth)
					|| (side == 2 && r > 3 * lineWidth))
				corner = Corner.UpRight;// east[5] || north[3]
			else if ((side == 2 && r < lineWidth)
					|| (side == 3 && r > 3 * lineWidth))
				corner = Corner.UpLeft;// north[5] || west[3]

			// small circles (r = 0.5 linneWidth) - clockwise
			// big circles (r = 3.5 linneWidth) - counterclockwise
			rd = (r < lineWidth) ? RotationDirection.Clockwise
					: RotationDirection.Counterclockwise;

			// save initial data for rotation
			TurningVehicle turn = new TurningVehicle(vehicle, corner, rd, r);
			turniningVehicles.put(vehicle, turn);
		}

		// add the vehicle
		allVehicles.add(vehicle);
		return true;
	}

	private void translateVehicle(Vehicle vehicle) {
		vehicle.setX(vehicle.getX() + vehicle.getDx());
		vehicle.setY(vehicle.getY() + vehicle.getDy());
		if (!inside(vehicle.getX(), vehicle.getY())) {
			out(vehicle);
		}
	}

	private void rotateVehicle(TurningVehicle vehicle) {
		vehicle.incTime();
		double r = vehicle.getR();
		double phi = vehicle.getTime() * speed / r;
		double x, y;
		x = y = 0;

		if (vehicle.getRotation() == RotationDirection.Clockwise) {
			phi = Math.PI / 2 - phi;
		}

		switch (vehicle.getCorner()) {
		case DownLeft: {
			x = r * Math.cos(phi);
			y = size - r * Math.sin(phi);
			break;
		}
		case DownRight: {
			x = size - r * Math.sin(phi);
			y = size - r * Math.cos(phi);
			break;
		}
		case UpLeft: {
			x = r * Math.sin(phi);
			y = r * Math.cos(phi);
			break;
		}
		case UpRight: {
			x = size - r * Math.cos(phi);
			y = r * Math.sin(phi);
			break;
		}
		}
		vehicle.getVehicle().setX(x);
		vehicle.getVehicle().setY(y);

		if (!inside(x, y)) {
			out(vehicle.getVehicle());
			turniningVehicles.remove(vehicle.getVehicle());
		}
	}

	/**
	 * Determines whether the point is in the square
	 * 
	 * @param x
	 * @param y
	 * @return <b>false</b> if the vehicle left the center of the crossroad
	 */
	private boolean inside(double x, double y) {
		return (x <= size) && (x >= 0) && (y <= size) && (y >= 0);
	}

	/**
	 * When vehicle leaves the square, put it in the connected street.
	 * 
	 * @param vehicle
	 */
	private void out(Vehicle vehicle) {
		double x = vehicle.getX();
		double y = vehicle.getY();

		if (x>size) {
			x=size+1;
		}else if (x<0) {
			x = -1;
		}
		if (y>size) {
			y=size+1;
		}else if (y<0) {
			y = -1;
		}
		
		int side = 0; // side - street
		int pos = 0; // line in the street

		// determines where to go...
		if (x < 0) {
			// go west
			side = 3;
			pos = (int) (Street.lineCount * y / this.size);
		}
		if (y < 0) {
			// go north
			side = 2;
			pos = (int) (Street.lineCount * (this.size - x) / this.size);
		}
		if (x > size) {
			// go east
			side = 1;
			pos = (int) (Street.lineCount * (this.size - y) / this.size);
		}
		if (y > size) {
			// go south
			side = 0;
			pos = (int) (Street.lineCount * x / this.size);
		}

		// side*Street.lineCount+pos -> id(line)

		this.tracks.get(side * Street.lineCount + pos)
				.addVehicle(vehicle, this);

		allVehicles.remove(vehicle);
	}

	@Override
	public String toString() {
		StringBuffer line = new StringBuffer("CrossroadCenter [speed=" + speed
				+ ", size=" + size + "]");
		Iterator<Vehicle> iter = this.allVehicles.iterator();

		while (iter.hasNext()) {
			line.append("\n\t" + iter.next().toString());
		}

		return line.toString();
	}

}
